<?php // $Id: backuplib.php,v 1.62.2.6 2008/02/05 10:16:40 jamiesensei Exp $
	//This php script contains all the stuff to backup quizzes

//This is the "graphical" structure of the quiz mod:
	//To see, put your terminal to 160cc

	//
	//						   quiz
	//						(CL,pk->id)
	//							|
	//		   -------------------------------------------------------------------
	//		   |			   |				|				|			   |
	//		   |		  quiz_grades		   |	 quiz_question_versions	 |
	//		   |	  (UL,pk->id,fk->quiz)	  |	  (CL,pk->id,fk->quiz)	  |
	//		   |								|								|
	//	  quiz_attempts			 quiz_question_instances				quiz_feedback
	//  (UL,pk->id,fk->quiz)	   (CL,pk->id,fk->quiz,question)		 (CL,pk->id,fk->quiz)
	//
	// Meaning: pk->primary key field of the table
	//		  fk->foreign key to link with parent
	//		  nt->nested field (recursive data)
	//		  SL->site level info
	//		  CL->course level info
	//		  UL->user level info
	//		  files->table may have files
	//
	//-----------------------------------------------------------

	// When we backup a quiz we also need to backup the questions and possibly
	// the data about student interaction with the questions. The functions to do
	// that are included with the following library
	require_once("$CFG->dirroot/question/backuplib.php");

	/*
	 * Insert necessary category ids to backup_ids table. Called during backup_check.html
	 * This backs up ids for quiz module. It backs up :
	 *	 all categories and questions in course
	 *	 all categories and questions in contexts of quiz module instances which have been selected for backup
	 *	 all categories and questions in contexts above course level that are used by quizzes that have been selected for backup
	 */
	function quiz_insert_category_and_question_ids($course, $backup_unique_code, $instances = null) {
		global $CFG;
		$status = true;
		
		// Create missing categories and reasign orphaned questions.
		quiz_fix_orphaned_questions($course);

		$coursecontext = get_context_instance(CONTEXT_COURSE, $course);
		$status = $status && question_insert_c_and_q_ids_for_course($coursecontext, $backup_unique_code);
		
		// then, all categories and questions from this course's modules' contexts.
		$status = $status && question_insert_c_and_q_ids_for_module($backup_unique_code, $course, 'quiz', $instances);

		// Then categories from parent contexts used by the quizzes we are backing up.
		//TODO this will need generalising when we have modules other than quiz using shared questions above course level.
		$parentcontexts = get_parent_contexts($coursecontext);
		$from = "{$CFG->prefix}quiz quiz,";
		$where = "AND quiz.course = '$course'
					 AND qqi.quiz = quiz.id";
		if (!empty($instances) && is_array($instances) && count($instances)) {
			$questionselectsqlfrom = '';
			$questionselectsqlwhere = 'AND qqi.quiz IN ('.implode(',',array_keys($instances)).')';
		} else {
			$questionselectsqlfrom = "{$CFG->prefix}quiz quiz,";
			$questionselectsqlwhere = "AND quiz.course = '$course'
						 AND qqi.quiz = quiz.id";
		}
		$categories = get_records_sql("
				SELECT id, parent, 0 AS childrendone
				FROM {$CFG->prefix}question_categories
				WHERE contextid IN (".join($parentcontexts, ', ').")
				  AND id IN (
					SELECT DISTINCT question.category
					FROM {$CFG->prefix}question question,
						 $questionselectsqlfrom
						 {$CFG->prefix}quiz_question_instances qqi
					WHERE qqi.question = question.id
					  $questionselectsqlwhere
				)", false);
		if (!$categories) {
			$categories = array();
		} else {
			//put the ids of the used questions from all these categories into the db.
			$status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids
									   (backup_code, table_name, old_id, info)
									   SELECT DISTINCT $backup_unique_code, 'question', q.id, ''
									   FROM {$CFG->prefix}question q,
									   $from
									   {$CFG->prefix}question_categories qc,
									   {$CFG->prefix}quiz_question_instances qqi
									   WHERE (qqi.question = q.id
									   OR qqi.question = q.parent)
									   AND q.category = qc.id
									   AND qc.contextid IN (".join($parentcontexts, ', ').")
									   $where", false);

			// Add the parent categories, of these categories up to the top of the category tree.
			// not backing up the questions in these categories.
			foreach ($categories as $category) {
				while ($category->parent != 0) {
					if (array_key_exists($category->parent, $categories)) {
						// Parent category already on the list.
						break;
					}
					$currentid = $category->id;
					$category = get_record('question_categories', 'id', $category->parent, '', '', '', '', 'id, parent, 0 AS childrendone');
					if ($category) {
						$categories[$category->id] = $category;
					} else {
						// Parent not found: this indicates an error, but just fix it.
						set_field('question_categories', 'parent', 0, 'id', $currentid);
						break;
					}
				}
			}

			// Now we look for categories from other courses containing random questions
			// in our quizzes that select from the category and its subcategories. That implies
			// those subcategories also need to be backed up. (The categories themselves
			// and their parents will already have been included.)
			$categorieswithrandom = get_records_sql("
					SELECT question.category AS id, SUM(" .
							sql_cast_char2int('questiontext', true) . ") AS numqsusingsubcategories
					FROM {$CFG->prefix}quiz_question_instances qqi,
						 $from
						 {$CFG->prefix}question question
					WHERE question.id = qqi.question
					  AND question.qtype = '" . RANDOM . "'
					  $where
					GROUP BY question.category
					");
			$randomselectedquestions = array();
			if ($categorieswithrandom) {
				foreach ($categorieswithrandom as $category) {
					if ($category->numqsusingsubcategories > 0) {
						$status = $status && quiz_backup_add_sub_categories($categories, $randomselectedquestions, $category->id);
					}
				}
				$returnval = get_records_sql("
					SELECT question.id
					FROM {$CFG->prefix}question question
					WHERE question.category IN  (".join(array_keys($categorieswithrandom), ', ').")");
				if ($returnval) {
					$randomselectedquestions += $returnval;
				}
			}

			// Finally, add all these extra categories to the backup_ids table.
			foreach ($categories as $category) {
				$status = $status && backup_putid($backup_unique_code, 'question_categories', $category->id, 0);
			}
			// Finally, add all these extra categories to the backup_ids table.
			foreach ($randomselectedquestions as $question) {
				$status = $status && backup_putid($backup_unique_code, 'question', $question->id, 0);
			}
		}
		return $status;
	}
	
	/**
	 * Helper function adding the id of all the subcategories of a category to an array.
	 */
	function quiz_backup_add_sub_categories(&$categories, &$questions, $categoryid) {
		global $CFG;
		$status = true;
		if ($categories[$categoryid]->childrendone) {
			return $status;
		}
		if ($subcategories = get_records('question_categories', 'parent', $categoryid, '', 'id, 0 AS childrendone')) {
			foreach ($subcategories as $subcategory) {
				if (!array_key_exists($subcategory->id, $categories)) {
					$categories[$subcategory->id] = $subcategory;
				}
				$status = $status && quiz_backup_add_sub_categories($categories, $questions, $subcategory->id);
			}
			$subcatlist = join(array_keys($subcategories), ',');
			$returnval = get_records_sql("
				SELECT question.id
				FROM {$CFG->prefix}question question
				WHERE question.category IN ($subcatlist)
				");
			if ($returnval) {
				$questions += $returnval;
			}
		}
		$categories[$categoryid]->childrendone = 1;
		return $status;
	}


	//This function is used to detect orphaned questions (pointing to a
	//non existing category) and to recreate such category. This function
	//is used by the backup process, to ensure consistency and should be
	//executed in the upgrade process and, perhaps in the health center.
	function quiz_fix_orphaned_questions ($course) {

		global $CFG;

		$categories = get_records_sql("SELECT DISTINCT t.category, t.category
									   FROM {$CFG->prefix}question t,
											{$CFG->prefix}quiz_question_instances g,
											{$CFG->prefix}quiz q
									   WHERE q.course = '$course' AND
											 g.quiz = q.id AND
											 g.question = t.id",false);
		if ($categories) {
			foreach ($categories as $key => $category) {
				$exist = get_record('question_categories','id', $key);
				//If the category doesn't exist
				if (!$exist) {
					//Build a new category
					$db_cat = new stdClass;
					// always create missing categories in course context
					$db_cat->contextid = get_context_instance(CONTEXT_COURSE, $course);
					$db_cat->name = get_string('recreatedcategory','',$key);
					$db_cat->info = get_string('recreatedcategory','',$key);
					$db_cat->stamp = make_unique_id_code();
					//Insert the new category
					$catid = insert_record('question_categories',$db_cat);
					unset ($db_cat);
					if ($catid) {
						//Reasign orphaned questions to their new category
						set_field ('question','category',$catid,'category',$key);
					}
				}
			}
		}
	}


//STEP 2. Backup quizzes and associated structures
	//	(course dependent)

	function quiz_backup_one_mod($bf,$preferences,$quiz) {
		$status = true;

		if (is_numeric($quiz)) {
			$quiz = get_record('quiz','id',$quiz);
		}

		//Start mod
		fwrite ($bf,start_tag("MOD",3,true));
		//Print quiz data
		fwrite ($bf,full_tag("ID",4,false,$quiz->id));
		fwrite ($bf,full_tag("MODTYPE",4,false,"quiz"));
		fwrite ($bf,full_tag("NAME",4,false,$quiz->name));
		fwrite ($bf,full_tag("INTRO",4,false,$quiz->intro));
		fwrite ($bf,full_tag("TIMEOPEN",4,false,$quiz->timeopen));
		fwrite ($bf,full_tag("TIMECLOSE",4,false,$quiz->timeclose));
		fwrite ($bf,full_tag("OPTIONFLAGS",4,false,$quiz->optionflags));
		fwrite ($bf,full_tag("PENALTYSCHEME",4,false,$quiz->penaltyscheme));
		fwrite ($bf,full_tag("ATTEMPTS_NUMBER",4,false,$quiz->attempts));
		fwrite ($bf,full_tag("ATTEMPTONLAST",4,false,$quiz->attemptonlast));
		fwrite ($bf,full_tag("GRADEMETHOD",4,false,$quiz->grademethod));
		fwrite ($bf,full_tag("DECIMALPOINTS",4,false,$quiz->decimalpoints));
		fwrite ($bf,full_tag("REVIEW",4,false,$quiz->review));
		fwrite ($bf,full_tag("QUESTIONSPERPAGE",4,false,$quiz->questionsperpage));
		fwrite ($bf,full_tag("SHUFFLEQUESTIONS",4,false,$quiz->shufflequestions));
		fwrite ($bf,full_tag("SHUFFLEANSWERS",4,false,$quiz->shuffleanswers));
		fwrite ($bf,full_tag("QUESTIONS",4,false,$quiz->questions));
		fwrite ($bf,full_tag("SUMGRADES",4,false,$quiz->sumgrades));
		fwrite ($bf,full_tag("GRADE",4,false,$quiz->grade));
		fwrite ($bf,full_tag("TIMECREATED",4,false,$quiz->timecreated));
		fwrite ($bf,full_tag("TIMEMODIFIED",4,false,$quiz->timemodified));
		fwrite ($bf,full_tag("TIMELIMIT",4,false,$quiz->timelimit));
		fwrite ($bf,full_tag("PASSWORD",4,false,$quiz->password));
		fwrite ($bf,full_tag("SUBNET",4,false,$quiz->subnet));
		fwrite ($bf,full_tag("POPUP",4,false,$quiz->popup));
		fwrite ($bf,full_tag("DELAY1",4,false,$quiz->delay1));
		fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2));
		//Now we print to xml question_instances (Course Level)
		$status = backup_quiz_question_instances($bf,$preferences,$quiz->id);
		//Now we print to xml quiz_feedback (Course Level)
		$status = backup_quiz_feedback($bf,$preferences,$quiz->id);
		//Now we print to xml question_versions (Course Level)
		$status = backup_quiz_question_versions($bf,$preferences,$quiz->id);
		//if we've selected to backup users info, then execute:
		//	- backup_quiz_grades
		//	- backup_quiz_attempts
		if (backup_userdata_selected($preferences,'quiz',$quiz->id) && $status) {
			$status = backup_quiz_grades($bf,$preferences,$quiz->id);
			if ($status) {
				$status = backup_quiz_attempts($bf,$preferences,$quiz->id);
			}
		}
		//End mod
		$status = fwrite ($bf,end_tag("MOD",3,true));

		return $status;
	}


	function quiz_backup_mods($bf,$preferences) {

		global $CFG;

		$status = true;

		//Iterate over quiz table
		$quizzes = get_records ("quiz","course",$preferences->backup_course,"id");
		if ($quizzes) {
			foreach ($quizzes as $quiz) {
				if (backup_mod_selected($preferences,'quiz',$quiz->id)) {
					$status = quiz_backup_one_mod($bf,$preferences,$quiz);
				}
			}
		}
		return $status;
	}

	//Backup quiz_question_instances contents (executed from quiz_backup_mods)
	function backup_quiz_question_instances ($bf,$preferences,$quiz) {
		$status = true;

		$quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id");
		//If there are question_instances
		if ($quiz_question_instances) {
			//Write start tag
			$status = fwrite ($bf,start_tag("QUESTION_INSTANCES",4,true));
			//Iterate over each question_instance
			foreach ($quiz_question_instances as $que_ins) {
				//Start question instance
				$status = fwrite ($bf,start_tag("QUESTION_INSTANCE",5,true));
				//Print question_instance contents
				fwrite ($bf,full_tag("ID",6,false,$que_ins->id));
				fwrite ($bf,full_tag("QUESTION",6,false,$que_ins->question));
				fwrite ($bf,full_tag("GRADE",6,false,$que_ins->grade));
				//End question instance
				$status = fwrite ($bf,end_tag("QUESTION_INSTANCE",5,true));
			}
			//Write end tag
			$status = fwrite ($bf,end_tag("QUESTION_INSTANCES",4,true));
		}
		return $status;
	}

	//Backup quiz_question_instances contents (executed from quiz_backup_mods)
	function backup_quiz_feedback ($bf,$preferences,$quiz) {
		$status = true;

		$quiz_feedback = get_records('quiz_feedback', 'quizid', $quiz, 'id');
		// If there are question_instances ...
		if ($quiz_feedback) {
			// Write start tag.
			$status = $status & fwrite($bf,start_tag('FEEDBACKS', 4, true));

			// Iterate over each question_instance.
			foreach ($quiz_feedback as $feedback) {

				//Start feedback instance
				$status = $status & fwrite($bf, start_tag('FEEDBACK',5,true));

				//Print question_instance contents.
				$status = $status & fwrite($bf, full_tag('ID', 6, false, $feedback->id));
				$status = $status & fwrite($bf, full_tag('QUIZID', 6, false, $feedback->quizid));
				$status = $status & fwrite($bf, full_tag('FEEDBACKTEXT', 6, false, $feedback->feedbacktext));
				$status = $status & fwrite($bf, full_tag('MINGRADE', 6, false, $feedback->mingrade));
				$status = $status & fwrite($bf, full_tag('MAXGRADE', 6, false, $feedback->maxgrade));

				// End feedback instance.
				$status = $status & fwrite($bf, end_tag('FEEDBACK', 5, true));
			}

			// Write end tag.
			$status = $status & fwrite($bf, end_tag('FEEDBACKS', 4, true));
		}
		return $status;
	}

	//Backup quiz_question_versions contents (executed from quiz_backup_mods)
	function backup_quiz_question_versions ($bf,$preferences,$quiz) {
		$status = true;

		$quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id");
		//If there are question_versions
		if ($quiz_question_versions) {
			//Write start tag
			$status = fwrite ($bf,start_tag("QUESTION_VERSIONS",4,true));
			//Iterate over each question_version
			foreach ($quiz_question_versions as $que_ver) {
				//Start question version
				$status = fwrite ($bf,start_tag("QUESTION_VERSION",5,true));
				//Print question_version contents
				fwrite ($bf,full_tag("ID",6,false,$que_ver->id));
				fwrite ($bf,full_tag("OLDQUESTION",6,false,$que_ver->oldquestion));
				fwrite ($bf,full_tag("NEWQUESTION",6,false,$que_ver->newquestion));
				fwrite ($bf,full_tag("ORIGINALQUESTION",6,false,$que_ver->originalquestion));
				fwrite ($bf,full_tag("USERID",6,false,$que_ver->userid));
				fwrite ($bf,full_tag("TIMESTAMP",6,false,$que_ver->timestamp));
				//End question version
				$status = fwrite ($bf,end_tag("QUESTION_VERSION",5,true));
			}
			//Write end tag
			$status = fwrite ($bf,end_tag("QUESTION_VERSIONS",4,true));
		}
		return $status;
	}


	//Backup quiz_grades contents (executed from quiz_backup_mods)
	function backup_quiz_grades ($bf,$preferences,$quiz) {
		$status = true;

		$quiz_grades = get_records("quiz_grades","quiz",$quiz,"id");
		//If there are grades
		if ($quiz_grades) {
			//Write start tag
			$status = fwrite ($bf,start_tag("GRADES",4,true));
			//Iterate over each grade
			foreach ($quiz_grades as $gra) {
				//Start grade
				$status = fwrite ($bf,start_tag("GRADE",5,true));
				//Print grade contents
				fwrite ($bf,full_tag("ID",6,false,$gra->id));
				fwrite ($bf,full_tag("USERID",6,false,$gra->userid));
				fwrite ($bf,full_tag("GRADEVAL",6,false,$gra->grade));
				fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$gra->timemodified));
				//End question grade
				$status = fwrite ($bf,end_tag("GRADE",5,true));
			}
			//Write end tag
			$status = fwrite ($bf,end_tag("GRADES",4,true));
		}
		return $status;
	}

	//Backup quiz_attempts contents (executed from quiz_backup_mods)
	function backup_quiz_attempts ($bf,$preferences,$quiz) {
		$status = true;

		$quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id");
		//If there are attempts
		if ($quiz_attempts) {
			//Write start tag
			$status = fwrite ($bf,start_tag("ATTEMPTS",4,true));
			//Iterate over each attempt
			foreach ($quiz_attempts as $attempt) {
				//Start attempt
				$status = fwrite ($bf,start_tag("ATTEMPT",5,true));
				//Print attempt contents
				fwrite ($bf,full_tag("ID",6,false,$attempt->id));
				fwrite ($bf,full_tag("UNIQUEID",6,false,$attempt->uniqueid));
				fwrite ($bf,full_tag("USERID",6,false,$attempt->userid));
				fwrite ($bf,full_tag("ATTEMPTNUM",6,false,$attempt->attempt));
				fwrite ($bf,full_tag("SUMGRADES",6,false,$attempt->sumgrades));
				fwrite ($bf,full_tag("TIMESTART",6,false,$attempt->timestart));
				fwrite ($bf,full_tag("TIMEFINISH",6,false,$attempt->timefinish));
				fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$attempt->timemodified));
				fwrite ($bf,full_tag("LAYOUT",6,false,$attempt->layout));
				fwrite ($bf,full_tag("PREVIEW",6,false,$attempt->preview));
				//Now write to xml the states (in this attempt)
				$status = backup_question_states ($bf,$preferences,$attempt->uniqueid);
				//Now write to xml the sessions (in this attempt)
				$status = backup_question_sessions ($bf,$preferences,$attempt->uniqueid);
				//End attempt
				$status = fwrite ($bf,end_tag("ATTEMPT",5,true));
			}
			//Write end tag
			$status = fwrite ($bf,end_tag("ATTEMPTS",4,true));
		}
		return $status;
	}

	function quiz_check_backup_mods_instances($instance,$backup_unique_code) {
		// the keys in this array need to be unique as they get merged...
		$info[$instance->id.'0'][0] = '<b>'.$instance->name.'</b>';
		$info[$instance->id.'0'][1] = '';

		//Categories
		$info[$instance->id.'1'][0] = get_string("categories","quiz");
		if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
			$info[$instance->id.'1'][1] = count($ids);
		} else {
			$info[$instance->id.'1'][1] = 0;
		}
		//Questions
		$info[$instance->id.'2'][0] = get_string("questionsinclhidden","quiz");
		if ($ids = question_ids_by_backup ($backup_unique_code)) {
			$info[$instance->id.'2'][1] = count($ids);
		} else {
			$info[$instance->id.'2'][1] = 0;
		}

		//Now, if requested, the user_data
		if (!empty($instance->userdata)) {
			//Grades
			$info[$instance->id.'3'][0] = get_string("grades");
			if ($ids = quiz_grade_ids_by_instance ($instance->id)) {
				$info[$instance->id.'3'][1] = count($ids);
			} else {
				$info[$instance->id.'3'][1] = 0;
			}
		}
		return $info;
	}

   ////Return an array of info (name,value)
/// $instances is an array with key = instanceid, value = object (name,id,userdata)
   function quiz_check_backup_mods($course,$user_data= false,$backup_unique_code,$instances=null) {
		//this function selects all the questions / categories to be backed up.
		quiz_insert_category_and_question_ids($course, $backup_unique_code, $instances);
		if ($course != SITEID){
			question_insert_site_file_names($course, $backup_unique_code);
		}
		if (!empty($instances) && is_array($instances) && count($instances)) {
			$info = array();
			foreach ($instances as $id => $instance) {
				$info += quiz_check_backup_mods_instances($instance,$backup_unique_code);
			}
			return $info;
		}
		//First the course data
		$info[0][0] = get_string("modulenameplural","quiz");
		if ($ids = quiz_ids ($course)) {
			$info[0][1] = count($ids);
		} else {
			$info[0][1] = 0;
		}
		//Categories
		$info[1][0] = get_string("categories","quiz");
		if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
			$info[1][1] = count($ids);
		} else {
			$info[1][1] = 0;
		}
		//Questions
		$info[2][0] = get_string("questions","quiz");
		if ($ids = question_ids_by_backup ($backup_unique_code)) {
			$info[2][1] = count($ids);
		} else {
			$info[2][1] = 0;
		}

		//Now, if requested, the user_data
		if ($user_data) {
			//Grades
			$info[3][0] = get_string("grades");
			if ($ids = quiz_grade_ids_by_course ($course)) {
				$info[3][1] = count($ids);
			} else {
				$info[3][1] = 0;
			}
		}

		return $info;
	}

	//Return a content encoded to support interactivities linking. Every module
	//should have its own. They are called automatically from the backup procedure.
	function quiz_encode_content_links ($content,$preferences) {

		global $CFG;

		$base = preg_quote($CFG->wwwroot,"/");

		//Link to the list of quizs
		$buscar="/(".$base."\/mod\/quiz\/index.php\?id\=)([0-9]+)/";
		$result= preg_replace($buscar,'$@QUIZINDEX*$2@$',$content);

		//Link to quiz view by moduleid
		$buscar="/(".$base."\/mod\/quiz\/view.php\?id\=)([0-9]+)/";
		$result= preg_replace($buscar,'$@QUIZVIEWBYID*$2@$',$result);

		//Link to quiz view by quizid
		$buscar="/(".$base."\/mod\/quiz\/view.php\?q\=)([0-9]+)/";
		$result= preg_replace($buscar,'$@QUIZVIEWBYQ*$2@$',$result);

		return $result;
	}

// INTERNAL FUNCTIONS. BASED IN THE MOD STRUCTURE

	//Returns an array of quiz id
	function quiz_ids ($course) {

		global $CFG;

		return get_records_sql ("SELECT a.id, a.course
								 FROM {$CFG->prefix}quiz a
								 WHERE a.course = '$course'");
	}

	function quiz_grade_ids_by_course ($course) {

		global $CFG;

		return get_records_sql ("SELECT g.id, g.quiz
								 FROM {$CFG->prefix}quiz a,
									  {$CFG->prefix}quiz_grades g
								 WHERE a.course = '$course' and
									   g.quiz = a.id");
	}

	function quiz_grade_ids_by_instance($instanceid) {

		global $CFG;

		return get_records_sql ("SELECT g.id, g.quiz
								 FROM {$CFG->prefix}quiz_grades g
								 WHERE g.quiz = $instanceid");
	}

?>
